Skip to content

fix: tie-robust border-ellipse extents so jit gradients are correct - #672

Merged
Jammy2211 merged 1 commit into
mainfrom
feature/rectangular-gradient-rtu-sparse
Nov 2, 2026
Merged

Jammy2211 merged 1 commit into
mainfrom
feature/rectangular-gradient-rtu-sparse

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

Summary

Fixes a wrong JAX gradient through the border relocator when the lens is centred on the mask's symmetry centre. This is the case for every cell evaluated at the prior medians. Closes #671.

What goes wrong: ellipse_params_via_border_pca_from sizes the relocation ellipse with a = max(|x′|) and b = max(|y′|). A point-symmetric lens (Isothermal + external shear) centred on the symmetry centre of the mask traces the border into exact ± pairs, so both maxima are exact ties. JAX's max gradient shares itself among the entries equal to the result. Under jit, XLA can recompute the operand inside a fused graph with different rounding, so that equality test is unstable. The reverse-mode gradient of b came out 2× or 0.5× its true value, while eager mode was correct. Every pixelized-source likelihood with use_border_relocator=True inherits this.

Measured (jitted Fitness, jax_test imaging, RectangularRTUAdaptImage 39x39, MGE-60 lens light, exact prior medians):

parameter before (autodiff / FD) after (autodiff / FD)
mass.einstein_radius 25.46 / 24.18 24.183 / 24.183 (1.4e-7)
mass.ell_comps_0 54.25 / 55.89 55.891 / 55.891
mass.ell_comps_1 41.68 / 41.92 41.922 / 41.922

Stage bisection: the traced grid had identical tangents under eager, jit and FD. The relocated grid diverged only under jit reverse mode, and inside it only b (eager 0.5392, jit 1.0908, FD 0.5392).

Fix: a new helper max_at_argmax_from takes the element at the argmax instead of a max reduction, so the whole gradient lands on one point, identically under eager and jit. Values are bit-identical to before. At a genuine tie, any single element's derivative is a valid one-sided derivative of the maximum.

Not changed: the kernel-CDF per-axis min/max and std(axis=0).min() in interpolator/rectangular.py are the same kind of reduction. They showed no eager/jit divergence in axis-aligned and round configurations, which do produce exact ties, so they are left as they are.

About the original #671 symptom (|g| ≈ 2e9 on the HST fiducial): that part is not a library bug. The 141x141 grid at pixelization over-sampling 1 has a point exactly at the Isothermal centre, where the psi softening (a 1e-8 core) makes the true derivative about 1e8, which FD cannot resolve. The evaluation-point fix belongs to the profiling campaign (autolens_profiling#509). Diagnosis is on #671.

API Changes

Bug fix to JAX gradients only; numerical values are unchanged.

  • Added autoarray.inversion.mesh.border_relocator.max_at_argmax_from(values, xp=np), a tie-robust maximum.
  • ellipse_params_via_border_pca_from uses it for the semi-axes a and b. Same values, corrected jit gradients.
    See full details below.

Test Plan

  • New test__max_at_argmax_from__tied_maximum_gradient_lands_on_one_element: one-hot gradient on a tied maximum, eager and jit. jnp.max fails it with a 0.5/0.5 split.
  • Full test_autoarray suite: 2268 passed, 4 xfailed.
  • End-to-end jitted repro: mass/shear gradients match FD (table above). Repro scripts in Science/scratch/rectangular_gradient_rtu_sparse_2026-11-02/.
  • Profiling Witness on the HST fiducial once autolens_profiling#509 moves the fiducial evaluation point off the grid (the odd grid's symmetry centre is a grid point).
Full API Changes (for automation & release notes)

Added

  • autoarray.inversion.mesh.border_relocator.max_at_argmax_from(values, xp=np): maximum of a 1D array taken as values[xp.argmax(values)], so its gradient is one-hot even at ties.

Changed

  • ellipse_params_via_border_pca_from: semi-axes a, b computed via max_at_argmax_from. Values are identical. Reverse-mode gradients under jit are now correct at tied extents (previously 2× / 0.5× off).

Migration

  • None.

Generated by the PyAutoLabs agent workflow.

🤖 Generated with Claude Code

…671)

ellipse_params_via_border_pca_from took the semi-axes as max(|x'|) and
max(|y'|). A lens centred on the mask's symmetry centre traces the border
into exact +/- pairs, so both maxima tie; JAX's max gradient finds tied
entries by equality, which is unstable under jit, and the reverse-mode
gradient of b came out 2x / 0.5x its true value (eager was correct).
Take the element at the argmax instead (max_at_argmax_from): same values,
one-hot gradient under eager and jit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Jammy2211 Jammy2211 added the pending-release PR queued for the next release build label Nov 2, 2026
@Jammy2211
Jammy2211 merged commit d7b642e into main Nov 2, 2026
3 checks passed
@Jammy2211
Jammy2211 deleted the feature/rectangular-gradient-rtu-sparse branch November 2, 2026 14:46
@pyautolabs-bot pyautolabs-bot Bot removed the pending-release PR queued for the next release build label Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: wrong JAX gradient through rectangular mesh (RTU all backends, Bilinear CPU sparse)

1 participant